//==============================================================================
//
//        DTextWritableLogger - the TextWritable logger in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-08-07 17:38:49 $ $Revision: 1.5 $
//
//==============================================================================

#include "ofc/DTextWritableLogger.h"


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DLogger.h"
#include "ofc/DTextWritable.h"

#include "ofc/DText.h"
#include "ofc/DDateTime.h"

//
// The DTextWritableLogger class implements methods for a continuous logging
// to a class implementing  the @proto(DTextWritable). The logger changes
// identical sequential log messages to two lines: first the message and then a line
// with the number of occurences. The layout of the log line is: 
// yyyy-mm-dd hh:mm:ss.mmm <level> message.
// 
// @example
// #include <stdio.h>
// #include "ofc/DTextWritableLogger.h"
// #include "ofc/DLog.h"
// #include "ofc/DFile.h"
// 
// int main(int argc, char *argv[])
// {
//   DTextWritableLogger *logger = [DTextWritableLogger new];
//   DFile               *file   = [DFile new];
//   char                 name[] = "example.log";
//   int                  i;
// 
//   if ([file open :name :"w"])
//   {
//     [logger writer :file];             // Give to the textwriteable logger the destination file
// 
//     dlogger(logger);                   // Give to the base logger the textwritable logger as destination
// 
//     dlog(DLOG_ALERT, "Alert!!");       // Log an alert
// 
//     for (i = 0; i < 10; i++)           // Add a repeated error message
//     {
//       dlog(DLOG_ERROR, "Errors");
//     }
// 
//     dlogmask(DLOG_NOTICE|DLOG_DEBUG);  // Mask some log levels
//     
//     dlog(DLOG_WARNING, "Warning..masked"); // Masked warning
//     dlog(DLOG_DEBUG,   "Debug:%d",7);  // Debug message
//     dlog(DLOG_NOTICE,  "Notice.");     // Notice message
// 
//     dlogger(nil);                      // Stop logging via the textwritable logger
// 
//     [file close];                      // Close the destination file
// 
//     printf("Events succesfully logged in \"%s\".\n", name);
//   }
//   else
//     printf("Could not open \"%s\" for writing:%d.\n", name, [file error]);
// 
//   [logger free];                       // Cleanup
//   [file   free];
// 
//   return 0;
// }
// 

@interface DTextWritableLogger : DObject <DLogger>
{
  unsigned            _mask;     // the active mask
  id<DTextWritable>   _writer;   // the reference to the writer
  DText              *_previous; // the previous message
  DDateTime          *_time;     // the time
  long                _repeated; // the number of identical messages
  DText              *_output;   // the output for the logger
}
#endif



@implementation DTextWritableLogger

  
//// Constructors

//
// Initialise an empty logger 
//
// @return the object
//

- (DTextWritableLogger *) init
{
  [super init];

  _writer   = nil;
  _previous = [DText     new];
  _output   = [DText     new];
  _time     = [DDateTime new];
  _repeated = 1;  
              // Enable all log levels
  _mask     = DLOG_PANIC | DLOG_ALERT   | DLOG_CRITICAL |
              DLOG_ERROR | DLOG_WARNING | DLOG_NOTICE   |
              DLOG_INFO  | DLOG_DEBUG;

  return self;
}

//
// Initialise a logger with a writter
// 
// @param writer     the writer for the logger
//
// @return the object
//

- (DTextWritableLogger *) init :(id <DTextWritable>) writer
{
  [self init];

  [self writer :writer];
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object
// 
// @return the object
//
- shallowCopy
{
  DTextWritableLogger *copy = [super shallowCopy];
  
  copy->_previous = [_previous copy];
  copy->_time     = [_time     copy];
  copy->_output   = [_output   copy];
  
  return copy;
}


//// Deconstructor

//
// Free the object
//
// @return the object
//

- free
{
  [_previous free]; _previous = nil;  
  [_time     free]; _time     = nil;
  [_output   free]; _output   = nil;
  
  return [super free];
}


//// Member methods

//
// Set the writer for the logger
// 
// @param writer    the writer for the logger
// 
// @return the object
// 
- (DTextWritableLogger *) writer :(id<DTextWritable>) writer
{
  _writer = writer;
  
  return self;
}


//// Implementation of interface DLogger

//
// Log an event by the logger
// 
// @param level      the level of the event
// @param message    the message to be logged
// 
// @return success
// 
- (BOOL) doLog :(int) level :(const char *) message
{
  BOOL ok = YES;
  
  if ((message == NULL) || (*message == EOS))
  {
    WARNING(DW_INVALID_ARG, "message");
  }
  else if ((_writer != nil) && ((level & _mask) != 0))
  {
    if ([_previous icompare :message] == 0)
    {
      _repeated++;
    }
    else
    {
      if (_repeated > 1)
      {
        [_previous format :"*** Repeated: %d ***", _repeated];
        
        ok = [self writeEvent :DLOG_UNKNOWN :[_previous cstring]];
      }
      _repeated = 1;

      if (ok)
      {
        ok = [self writeEvent :level :message];
      }
      
      [_previous set :message];
    }
  }
  
  return ok;
}

//
// Set the active levels 
// 
// @param level      the bitwise or'ed active levels 
// 
// @return the previous mask
// 
- (int) mask :(int) levels
{
  int prev = _mask;
  
  _mask = levels;
  
  return prev;
}

//
// Set the active level range
// 
// @param high        the highest active level
// @param low         the lowest active level
// 
// @return the previous mask
//
- (int) mask :(int) high :(int) low
{
  int prev = _mask;
  
  if (high < low)
  {
    WARNING(DW_INVALID_ARG, "low");
  }
  else
  {
    unsigned bit = low;
    
    _mask = 0;
    
    while (bit <= high)
    {
      _mask |= bit;
      
      bit <<= 1;
    }
  }
  
  return prev;
}

//
// Write a event in the writer
// 
// @param level       the level of the event   
// @param message     the message of the event
// 
// @return success
// 
- (BOOL) writeEvent :(int) level :(const char *) message
{
  BOOL   ok   = NO;

  [_output clear];
  
  [_time localTime];
  
  if ([self format :_output :_time :level :message])
  {
    ok = [_writer writeLine :[_output cstring]];
  }

  return ok;
}

//
// Format the output of the event (to be overridden for different format)
// 
// @param output      the output of the formatting of the event
// @param dateTime    the date/time of the event
// @param level       the level of the event   
// @param message     the message of the event
// 
// @return success
// 
- (BOOL) format :(DText *) output :(DDateTime *) dateTime :(int) level :(const char *) message
{
  DText *text = nil;

  // Put the date/time in the output
  text = [_time toText];
        
  if (text != nil)
  {
    [output set :[text cstring]];
    
    [text free];
  }
  
  // Put the level in the output
  switch (level)
  {
   case DLOG_PANIC:
    [output append :" PANIC    "]; break;
   case DLOG_ALERT:
    [output append :" ALERT    "]; break;
   case DLOG_CRITICAL:
    [output append :" CRITICAL "]; break;
   case DLOG_ERROR:
    [output append :" ERROR    "]; break;
   case DLOG_WARNING:
    [output append :" WARNING  "]; break;
   case DLOG_NOTICE:
    [output append :" NOTICE   "]; break;
   case DLOG_INFO:
    [output append :" INFO     "]; break;
   case DLOG_DEBUG:
    [output append :" DEBUG    "]; break;
   default:
    [output append :" ******** "]; break;
  }

  // Put the message in the output
  [output append :message];
  
  return YES;
}

@end

/*==========================================================================*/
